package com.zyl.blog.rbac.service.impl.rbac;

import cn.dev33.satoken.secure.SaSecureUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.bean.BeanUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.github.yulichang.base.MPJBaseServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.zyl.blog.common.domain.data.vo.LogVO;
import com.zyl.blog.common.domain.rbac.bean.Permission;
import com.zyl.blog.common.domain.rbac.bean.Role;
import com.zyl.blog.common.domain.rbac.bean.User;
import com.zyl.blog.common.domain.rbac.dto.LoginDTO;
import com.zyl.blog.common.domain.rbac.dto.RegisterDTO;
import com.zyl.blog.common.domain.rbac.dto.UserDTO;
import com.zyl.blog.common.domain.rbac.enums.AuthType;
import com.zyl.blog.common.domain.rbac.vo.LoginResultVO;
import com.zyl.blog.common.domain.rbac.vo.perm.PermVO;
import com.zyl.blog.common.domain.rbac.vo.role.RoleVO;
import com.zyl.blog.common.domain.rbac.vo.user.UserInfoVO;
import com.zyl.blog.common.domain.related.bean.RolePerm;
import com.zyl.blog.common.domain.related.bean.RoleRoute;
import com.zyl.blog.common.domain.related.bean.UserRole;
import com.zyl.blog.common.domain.router.bean.Route;
import com.zyl.blog.common.domain.router.vo.RouteVO;
import com.zyl.blog.common.enums.ResultCode;
import com.zyl.blog.common.exception.BlogException;
import com.zyl.blog.common.response.Result;
import com.zyl.blog.common.utils.Extreme.*;
import com.zyl.blog.rbac.mapper.rbac.UserMapper;
import com.zyl.blog.rbac.service.rbac.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @version v1.0
 * @ProjectName: Personal_Blog
 * @Package: com.zyl.service.impl
 * @ClassName: IRbacService
 * @Description:
 * @Author FinalFantasy
 * @Date 2025/6/23-15:56
 */
@RequiredArgsConstructor
@Service
public class IUserService extends MPJBaseServiceImpl<UserMapper, User>
    implements UserService {

    private final UserMapper userMapper;

    /* redis工具类 */
    private final RedisUtil redisUtil;

    @Value("${login.max-attempts:5}")
    private int maxAttempts;

    @Value("${login.lock-time:30}")
    private int lockTime;

    /**
     * 用户登录业务实现
     * @Transactional 添加事务管理
     * @businessLogic 包含以下流程：
     * 1. 用户名密码校验
     * 2. 更新用户登录信息
     * 3. 返回用户信息
     */
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    @Override
    public LoginResultVO doLogin(LoginDTO loginDTO) {

        /* 检查是否被锁定 */
        checkLoginAttempts(loginDTO.identifier());

        User user = queryUserByAuthType(loginDTO);

        if( user == null ) {
            handleLoginFailure(loginDTO.identifier());
            LogUtil.warn("用户登录失败，用户不存在😯");
            throw new BlogException("用户不存在😯");
        }

        /* 验证用户名 */
        if( Integer.valueOf(0).equals(user.getStatus())) {
            LogUtil.warn("用户登录失败，用户{} 已被{}", user.getUsername(), "禁用");
            throw new BlogException("该用户已经被禁用,请联系管理员处理🙏");
        }

        /* 验证密码 */
        if( !user.getPasswordHash().equals(SaSecureUtil.sha256(loginDTO.password()))) {
            handleLoginFailure(loginDTO.identifier());
            LogUtil.warn("用户登录失败，密码不正确💢");
            throw new BlogException("密码不正确💢");
        }

        /* 验证成功，清除登录尝试记录 */
        clearLoginAttempts(loginDTO.identifier());

        /* 更新登录时间 */
        updateLoginInfo(user);

        /* 最后执行登录 */
        StpUtil.login(user.getUserId());

        return LoginResultVO.builder()
                .token(StpUtil.getTokenValue())
                .expiresIn(StpUtil.getTokenTimeout() / 1000)
                .id(user.getUserId())
                .build();
    }

    @Override
    public Result<?> doRegister(RegisterDTO registerDTO) {
        /* 验证码校验 */
        if (!CaptchaUtils.validateAndDelete(registerDTO.captchaKey(), registerDTO.captcha())) {
            throw new BlogException(ResultCode.CAPTCHA_ERROR);
        }

        /* 检查用户是否已存在 */
        checkUserExists(registerDTO);

        /* 构建用户对象 */
        User user = User.buildUser(registerDTO);

        /* 存储对象 */
        userMapper.insert(user);

        return Result.ok().msg("注册成功🎉");
    }

    @Override
    public UserInfoVO fetchUserInfoWithAllRelations(Long userId) {
        MPJLambdaWrapper<User> wrapper = new MPJLambdaWrapper<>();
        wrapper.selectAll(User.class)
                .selectCollection(Role.class, User::getRoles)
                .selectCollection(Permission.class, User::getPermissions)
                .selectCollection(Route.class, User::getRoutes)
                .leftJoin(UserRole.class, UserRole::getUserId, User::getUserId)
                .leftJoin(Role.class, Role::getRoleId, UserRole::getRoleId)
                .leftJoin(RolePerm.class, RolePerm::getRoleId, Role::getRoleId)
                .leftJoin(Permission.class, Permission::getPermissionId, RolePerm::getPermissionId)
                .leftJoin(RoleRoute.class, RoleRoute::getRoleId, Role::getRoleId)
                .leftJoin(Route.class, Route::getId, RoleRoute::getRouteId)
                .eq(User::getUserId, userId);

        User currentUser = userMapper.selectJoinOne(wrapper);
        UserInfoVO userInfo = BeanUtil.copyProperties(currentUser, UserInfoVO.class);
        return userInfo
                .setRoles(
                        TreeUtils.buildTree(
                                RoleVO.toVO(currentUser.getRoles()),
                                RoleVO.TREE_CONFIG
                        )
                )
                .setRoutes(
                        TreeUtils.buildTree(
                                RouteVO.toVO(currentUser.getRoutes()),
                                RouteVO.TREE_CONFIG
                        )
                )
                .setPermissions(
                        TreeUtils.buildTree(
                                PermVO.toVO(currentUser.getPermissions()),
                                PermVO.TREE_CONFIG
                        )
                );
    }

    /**
     * 获取用户信息（加密）
     * @param userId 用户ID
     * @return {@link String } 返回加密后的用户信息字符串
     */
    @Override
    public String getUserInfo(Long userId) {
        return EncryptUtil.encrypt(fetchUserInfoWithAllRelations(userId));
    }

    @Override
    public UserInfoVO getUserInfoAll(Long userId) {
        return fetchUserInfoWithAllRelations(userId);
    }

    @Override
    public LogVO getLogVO(Long userId) {
        // 查询用户信息
        User user = userMapper.selectById(userId);
        if (user == null) {
            return new LogVO(userId, "未知用户", "未知部门");
        }

        // 构建并返回LogVO对象
        return new LogVO(
                user.getUserId(),
                user.getNickname() != null ? user.getNickname() : user.getUsername(),
                null
        );
    }

    @Override
    public Result<?> addUser(UserDTO dto) {
        return null;
    }

    @Override
    public Result<?> updateUser(UserDTO dto) {
        return null;
    }

    @Override
    public Result<?> logout() {
        /* 检查当前是否登录 */
        if (!StpUtil.isLogin()) {
            return Result.ok().msg("未登录状态，无需退出");
        }

        /* 获取当前用户ID */
        Long userId = StpUtil.getLoginIdAsLong();

        /* 记录退出日志（企业级应用通常需要） */
        LogUtil.info("用户[({})]主动退出登录", userId);

        /* 清除当前会话 */
        StpUtil.logout();

        /* 清除用户相关的临时缓存（如用户信息缓存等） */
        clearUserTempCache(userId);

        return Result.ok().msg("退出登录成功");
    }

    @Override
    public Result<?> forceLogoutAll() {
        if (!StpUtil.isLogin()) {
            return Result.ok().msg("未登录状态");
        }

        Long userId = StpUtil.getLoginIdAsLong();

        /* 记录强制退出日志*/
        LogUtil.info("用户[({})]执行了强制退出所有端操作", userId);

        /* 强制退出该用户的所有登录会话 */
        StpUtil.logout(userId);

        /* 清除用户所有相关缓存 */
        clearUserAllCache(userId);

        return Result.ok().msg("已强制退出所有端的登录");
    }

    /**
     * 根据认证类型查询用户
     */
    private User queryUserByAuthType(LoginDTO loginDTO) {
        AuthType authType = loginDTO.getAuthType();
        LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
        switch (authType) {
            case USERNAME:
                queryWrapper.eq(User::getUsername, loginDTO.identifier());
                break;
            case EMAIL:
                queryWrapper.eq(User::getEmail, loginDTO.identifier());
                break;
            case PHONE:
                queryWrapper.eq(User::getPhone, loginDTO.identifier());
                break;
            default:
                throw new UnsupportedOperationException("不支持的认证类型: " + authType);
        }

        return userMapper.selectOne(queryWrapper);
    }

    /**
     * 检查登录尝试次数
     */
    private void checkLoginAttempts(String identifier) {
        String key = getLoginAttemptsKey(identifier);
        Integer attempts = (Integer) redisUtil.get(key);

        if (attempts != null && attempts >= maxAttempts) {
            /* 获取剩余锁定时间 （秒） */
            long remainSeconds = redisUtil.getExpire(key);

            if ( remainSeconds <= 0 ) {
                /* 过期时间异常，强制清除记录 */
                clearLoginAttempts(identifier);
                return;
            }

            /* 转换分秒格式 */
            long minutes = remainSeconds / 60;
            long seconds = remainSeconds % 60;

            String remainTime = minutes > 0 ?
                    String.format("%d 分钟 %d 秒", minutes, seconds) :
                    String.format("%d 秒", seconds);

            throw new BlogException("登录失败次数过多，还需" + remainTime + "才能解锁");
        }
    }

    /**
     * 处理登录失败
     */
    private void handleLoginFailure(String identifier) {
        String key = getLoginAttemptsKey(identifier);
        Integer attempts = (Integer) redisUtil.get(key);

        if (attempts == null) {
            redisUtil.set(key, 1, lockTime, TimeUnit.MINUTES);
        } else {
            redisUtil.incr(key, 1);
            redisUtil.expire(key, lockTime, TimeUnit.MINUTES);
        }
    }

    /**
     * 清除登录尝试记录
     */
    private void clearLoginAttempts(String identifier) {
        redisUtil.del(getLoginAttemptsKey(identifier));
    }

    /**
     * 更新用户登录信息
     */
    private void updateLoginInfo(User user) {
        user.setLastLoginTime(LocalDateTime.now());
        user.setLoginCount(user.getLoginCount() + 1);
        userMapper.updateById(user);
    }

    /**
     * 获取登录尝试次数的Redis键
     */
    private String getLoginAttemptsKey(String identifier) {
        return "login:attempts:" + identifier;
    }

    /**
     * 检查用户是否已存在
     */
    private void checkUserExists(RegisterDTO dto) {
        User existingUser = switch (dto.authType()) {
            case USERNAME -> userMapper.selectOne(
                    new LambdaQueryWrapper<User>()
                            .eq(User::getUsername, dto.username())
            );
            case EMAIL -> userMapper.selectOne(
                    new LambdaQueryWrapper<User>()
                            .eq(User::getEmail, dto.email())
            );
            case PHONE -> userMapper.selectOne(
                    new LambdaQueryWrapper<User>()
                            .eq(User::getPhone, dto.phone())
            );
            default -> null;
        };

        if (existingUser != null) {
            throw new BlogException("该" + dto.authType().getDescription() + "已被注册");
        }
    }

    /**
     * 清除用户临时缓存
     */
    private void clearUserTempCache(Long userId) {
        try {
            /* 清除用户信息缓存（根据实际项目中的缓存key设计） */
            redisUtil.del("user:info:" + userId);

            /* 清除用户权限缓存（如果有） */
            redisUtil.del("user:permissions:" + userId);

            /* 其他需要清除的临时缓存 */
        } catch (Exception e) {
            LogUtil.error("清除用户临时缓存失败", e);
        }
    }

    /**
     * 清除用户所有相关缓存（适配补充后的 RedisUtil.keys 方法）
     */
    private void clearUserAllCache(Long userId) {
        try {
            /* 清除用户自身相关缓存（前缀：user:*:用户ID） */
            Set<String> userKeys = redisUtil.scan("user:*:" + userId);
            if (userKeys != null && !userKeys.isEmpty()) {
                redisUtil.del(userKeys.toArray(new String[0]));
                LogUtil.info("清除用户[{}]自身缓存，共{}个键", userId, userKeys.size());
            }

            /* 清除用户关联的角色缓存（前缀：role:users:*:用户ID）*/
            Set<String> roleKeys = redisUtil.scan("role:users:*:" + userId);
            if (roleKeys != null && !roleKeys.isEmpty()) {
                redisUtil.del(roleKeys.toArray(new String[0]));
                LogUtil.info("清除用户[{}]关联角色缓存，共{}个键", userId, roleKeys.size());
            }

        } catch (Exception e) {
            LogUtil.error("清除用户[{}]所有缓存失败", new Throwable(e), userId);
        }
    }
}
